/**
 *  Zebra_DatePicker
 *
 *  Zebra_DatePicker is a small, compact and highly configurable date picker plugin for jQuery
 *
 *  Visit {@link http://stefangabos.ro/jquery/zebra-datepicker/} for more information.
 *
 *  For more resources visit {@link http://stefangabos.ro/}
 *
 *  @author     Stefan Gabos <contact@stefangabos.ro>
 *  @version    1.5 (last revision: September 30, 2012)
 *  @copyright  (c) 2011 - 2012 Stefan Gabos
 *  @license    http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE
 *  @package    Zebra_DatePicker
 *  @addon by Ray on timer
 */
(function($) {
	$.Zebra_DatePicker = function(element, options) {
		var defaults = {
			//  by default, the button for clearing a previously selected date is shown only if a previously selected date
			//  already exists; this means that if the input the date picker is attached to is empty, and the user selects
			//  a date for the first time, this button will not be visible; once the user picked a date and opens the date
			//  picker again, this time the button will be visible.
			//
			//  setting this property to TRUE will make this button visible all the time
			always_show_clear : true,
			//  days of the week; Sunday to Saturday
			days : [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ],
			showdays : [ '日', '一', '二', '三', '四', '五', '六' ],
			//  direction of the calendar
			//
			//  a positive or negative integer: n (a positive integer) creates a future-only calendar beginning at n days
			//  after today; -n (a negative integer); if n is 0, the calendar has no restrictions. use boolean true for
			//  a future-only calendar starting with today and use boolean false for a past-only calendar ending today.
			//
			//  you may also set this property to an array with two elements in the following combinations:
			//
			//  -   first item is boolean TRUE (calendar starts today), an integer > 0 (calendar starts n days after
			//      today), or a valid date given in the format defined by the "format" attribute (calendar starts at the
			//      specified date), and the second item is boolean FALSE (the calendar has no ending date), an integer
			//      > 0 (calendar ends n days after the starting date), or a valid date given in the format defined by
			//      the "format" attribute and which occurs after the starting date (calendar ends at the specified date)
			//
			//  -   first item is boolean FALSE (calendar ends today), an integer < 0 (calendar ends n days before today),
			//      or a valid date given in the format defined by the "format" attribute (calendar ends at the specified
			//      date), and the second item is an integer > 0 (calendar ends n days before the ending date), or a valid
			//      date given in the format defined by the "format" attribute and which occurs before the starting date
			//      (calendar starts at the specified date)
			//
			//  [1, 7] - calendar starts tomorrow and ends seven days after that
			//  [true, 7] - calendar starts today and ends seven days after that
			//  ['2013-01-01', false] - calendar starts on January 1st 2013 and has no ending date ("format" is YYYY-MM-DD)
			//  [false, '2012-01-01'] - calendar ends today and starts on January 1st 2012 ("format" is YYYY-MM-DD)
			//
			//  note that "disabled_dates" property will still apply!
			//
			//  default is 0 (no restrictions)
			direction : 0,
			//  an array of disabled dates in the following format: 'day month year weekday' where "weekday" is optional
			//  and can be 0-6 (Saturday to Sunday); the syntax is similar to cron's syntax: the values are separated by
			//  spaces and may contain * (asterisk) - (dash) and , (comma) delimiters:
			//
			//  ['1 1 2012'] would disable January 1, 2012;
			//  ['* 1 2012'] would disable all days in January 2012;
			//  ['1-10 1 2012'] would disable January 1 through 10 in 2012;
			//  ['1,10 1 2012'] would disable January 1 and 10 in 2012;
			//  ['1-10,20,22,24 1-3 *'] would disable 1 through 10, plus the 22nd and 24th of January through March for every year;
			//  ['* * * 0,6'] would disable all Saturdays and Sundays;
			//  ['01 07 2012', '02 07 2012', '* 08 2012'] would disable 1st and 2nd of July 2012, and all of August of 2012
			//
			//  default is FALSE, no disabled dates
			disabled_dates : false,
			//  week's starting day
			//
			//  valid values are 0 to 6, Sunday to Saturday
			//
			//  default is 1, Monday
			first_day_of_week : 0,
			//  format of the returned date
			//
			//  accepts the following characters for date formatting: d, D, j, l, N, w, S, F, m, M, n, Y, y borrowing
			//  syntax from (PHP's date function)
			//
			//  note that when setting a date format without days ('d', 'j'), the users will be able to select only years
			//  and months, and when setting a format without months and days ('F', 'm', 'M', 'n', 't', 'd', 'j'), the
			//  users will be able to select only years.
			//
			//  also note that the value of the "view" property (see below) may be overridden if it is the case: a value of
			//  "days" for the "view" property makes no sense if the date format doesn't allow the selection of days.
			//
			//  default is Y-m-d
			format : 'Y-m-d',
			//  should the icon for opening the datepicker be inside the element?
			//  if set to FALSE, the icon will be placed to the right of the parent element, while if set to TRUE it will
			//  be placed to the right of the parent element, but *inside* the element itself
			//
			//  default is TRUE
			inside : true,
			//  months names
			months : [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September',
					'October', 'November', 'December' ],
			showmonths : [ '一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月' ],
			//  the offset, in pixels (x, y), to shift the date picker's position relative to the top-left of the icon
			//  that toggles the date picker
			//
			//  default is [20, -5]
			//	暂时取消此参数
			offset : [ 20, 20 ],
			//the z-index value of the datepicker dom
			zIndex : 99999,
			//  if set as a jQuery element with a Zebra_Datepicker attached, that particular date picker will use the
			//  current date picker's value as starting date
			//  note that the rules set in the "direction" property will still apply, only that the reference date will
			//  not be the current system date but the value selected in the current date picker
			//  default is FALSE (not paired with another date picker)
			pair : false,
			//  should the element the calendar is attached to, be read-only?
			//  if set to TRUE, a date can be set only through the date picker and cannot be entered manually
			//
			//  default is TRUE
			readonly_element : true,
			//  should an extra column be shown, showing the number of each week?
			//  anything other than FALSE will enable this feature, and use the given value as column title
			//  i.e. show_week_number: 'Wk' would enable this feature and have "Wk" as the column's title
			//
			//  default is FALSE
			show_week_number : false,
			//  a default date to start the date picker with
			//  must be specified in the format defined by the "format" property, or it will be ignored!
			//  note that this value is used only if there is no value in the field the date picker is attached to!
			start_date : false,
			//  how should the date picker start; valid values are "days", "months" and "years"
			//  note that the date picker is always cycling days-months-years when clicking in the date picker's header,
			//  and years-months-days when selecting dates (unless one or more of the views are missing due to the date's
			//  format)
			//
			//  also note that the value of the "view" property may be overridden if the date's format requires so! (i.e.
			//  "days" for the "view" property makes no sense if the date format doesn't allow the selection of days)
			//
			//  default is "days"
			view : 'days',
			//  days of the week that are considered "weekend days"
			//  valid values are 0 to 6, Sunday to Saturday
			//
			//  default values are 0 and 6 (Saturday and Sunday)
			weekend_days : [ 0, 6 ],
			//  callback function to be executed when a date is selected
			//  the callback function takes 3 arguments:
			//  -   the date in the format specified by the "format" attribute;
			//  -   the date in YYYY-MM-DD format
			//  -   the date as a JavaScript Date object
			onSelect : null,
			//  callback function to be executed when a month is rendered
			//  the callback function takes 3 arguments:
			//  -   the date in the format specified by the "format" attribute;
			//  -   the date in YYYY-MM-DD format
			//  -   the date as a JavaScript Date object
			onChange : null
		};
		// private properties
		var view, datepicker, icon, header, daypicker, monthpicker, yearpicker, footer, 
			timepickers, current_system_month, current_system_year, current_system_day, 
			first_selectable_month, first_selectable_year, first_selectable_day, 
			selected_month, selected_year, default_day, default_month, default_year, 
			disabled_dates, shim, start_date, end_date, last_selectable_day, 
			last_selectable_year, last_selectable_month, views;
		var plugin = this;
		plugin.settings = {};
		// the jQuery version of the element
		// "element" (without the $) will point to the DOM element
		var $element = $(element);
		/**
		 *  Constructor method. Initializes the date picker.
		 *
		 *  @return void
		 */
		var init = function(update) {
			// merge default settings with user-settings (unless we're just updating settings)
			if (!update)
				plugin.settings = $.extend({}, defaults, options);
			// if the element should be read-only, set the "readonly" attribute
			if (plugin.settings.readonly_element)
				$element.attr('readonly', 'readonly');
			// determine the views the user can cycle through, depending on the format
			// that is, if the format doesn't contain the day, the user will be able to cycle only through years and months,
			// whereas if the format doesn't contain months nor days, the user will only be able to select years
			var
			// the characters that may be present in the date format and that represent days, months and years
			date_chars = {
				days : [ 'd', 'j' ],
				months : [ 'F', 'm', 'M', 'n', 't' ],
				years : [ 'o', 'Y', 'y' ]
			},
			// some defaults
			has_days = false, has_months = false, has_years = false;
			// iterate through all the character blocks
			for (type in date_chars)
				// iterate through the characters of each block
				$.each(date_chars[type], function(index, character) {
					// if current character exists in the "format" property
					if (plugin.settings.format.indexOf(character) > -1)
						// set to TRUE the appropriate flag
						if (type == 'days')
							has_days = true;
						else if (type == 'months')
							has_months = true;
						else if (type == 'years')
							has_years = true;
				});
			// if user can cycle through all the views, set the flag accordingly
			if (has_days && has_months && has_years)
				views = [ 'years', 'months', 'days' ];
			// if user can cycle only through year and months, set the flag accordingly
			else if (!has_days && has_months && has_years)
				views = [ 'years', 'months' ];
			// if user can only see the year picker, set the flag accordingly
			else if (!has_days && !has_months && has_years)
				views = [ 'years' ];
			// if invalid format (no days, no months, no years) use the default where the user is able to cycle through
			// all the views
			else
				views = [ 'years', 'months', 'days' ];
			// if the starting view is not amongst the views the user can cycle through, set the correct starting view
			if ($.inArray(plugin.settings.view, views) == -1)
				plugin.settings.view = views[views.length - 1];
			var
			// cache the current system date
			date = new Date(),
			// when the date picker's starting date depends on the value of another date picker, this value will be
			// set by the other date picker
			// this value will be used as base for all calculations (if not set, will be the same as the current
			// system date)
			reference_date = (!plugin.settings.reference_date ? ($element.data('zdp_reference_date') ? $element
					.data('zdp_reference_date') : date) : plugin.settings.reference_date), tmp_start_date, tmp_end_date;
			// reset these values here as this method might be called more than once during a date picker's lifetime
			// (when the selectable dates depend on the values from another date picker)
			start_date = undefined;
			end_date = undefined;
			// extract the date parts
			// also, save the current system month/day/year - we'll use them to highlight the current system date
			first_selectable_month = reference_date.getMonth();
			current_system_month = date.getMonth();
			first_selectable_year = reference_date.getFullYear();
			current_system_year = date.getFullYear();
			first_selectable_day = reference_date.getDate();
			current_system_day = date.getDate();
			// check if the calendar has any restrictions
			// calendar is future-only, starting today
			// it means we have a starting date (the current system date), but no ending date
			if (plugin.settings.direction === true)
				start_date = reference_date;
			// calendar is past only, ending today
			else if (plugin.settings.direction === false) {
				// it means we have an ending date (the reference date), but no starting date
				end_date = reference_date;
				// extract the date parts
				last_selectable_month = end_date.getMonth();
				last_selectable_year = end_date.getFullYear();
				last_selectable_day = end_date.getDate();
			} else if (
			// if direction is not given as an array and the value is an integer > 0
			(!$.isArray(plugin.settings.direction) && is_integer(plugin.settings.direction) && to_int(plugin.settings.direction) > 0)
					||
					// or direction is given as an array
					($.isArray(plugin.settings.direction) && (
					// and first entry is boolean TRUE
					plugin.settings.direction[0] === true ||
					// or an integer > 0
					(is_integer(plugin.settings.direction[0]) && plugin.settings.direction[0] > 0) ||
					// or a valid date
					(tmp_start_date = check_date(plugin.settings.direction[0]))) && (
					// and second entry is boolean FALSE
					plugin.settings.direction[1] === false ||
					// or integer >= 0
					(is_integer(plugin.settings.direction[1]) && plugin.settings.direction[1] >= 0) ||
					// or a valid date
					(tmp_end_date = check_date(plugin.settings.direction[1]))))) {
				// if an exact starting date was given, use that as a starting date
				if (tmp_start_date)
					start_date = tmp_start_date;
				// otherwise
				else
					// figure out the starting date
					// use the Date object to normalize the date
					// for example, 2011 05 33 will be transformed to 2011 06 02
					start_date = new Date(first_selectable_year, first_selectable_month, first_selectable_day
							+ (!$.isArray(plugin.settings.direction) ? to_int(plugin.settings.direction)
									: to_int(plugin.settings.direction[0] === true ? 0 : plugin.settings.direction[0])));
				// re-extract the date parts
				first_selectable_month = start_date.getMonth();
				first_selectable_year = start_date.getFullYear();
				first_selectable_day = start_date.getDate();
				// if an exact ending date was given and the date is after the starting date, use that as a ending date
				if (tmp_end_date && +tmp_end_date > +start_date)
					end_date = tmp_end_date;
				// if have information about the ending date
				else if (!tmp_end_date && plugin.settings.direction[1] !== false
						&& $.isArray(plugin.settings.direction))
					// figure out the ending date
					// use the Date object to normalize the date
					// for example, 2011 05 33 will be transformed to 2011 06 02
					end_date = new Date(first_selectable_year, first_selectable_month, first_selectable_day
							+ to_int(plugin.settings.direction[1]));
				// if a valid ending date exists
				if (end_date) {
					// extract the date parts
					last_selectable_month = end_date.getMonth();
					last_selectable_year = end_date.getFullYear();
					last_selectable_day = end_date.getDate();
				}
			} else if (
			// if direction is not given as an array and the value is an integer < 0
			(!$.isArray(plugin.settings.direction) && is_integer(plugin.settings.direction) && to_int(plugin.settings.direction) < 0)
					||
					// or direction is given as an array
					($.isArray(plugin.settings.direction) && (
					// and first entry is boolean FALSE
					plugin.settings.direction[0] === false ||
					// or an integer < 0
					(is_integer(plugin.settings.direction[0]) && plugin.settings.direction[0] < 0)) && (
					// and second entry is integer >= 0
					(is_integer(plugin.settings.direction[1]) && plugin.settings.direction[1] >= 0) ||
					// or a valid date
					(tmp_start_date = check_date(plugin.settings.direction[1]))))) {
				// figure out the ending date
				// use the Date object to normalize the date
				// for example, 2011 05 33 will be transformed to 2011 06 02
				end_date = new Date(first_selectable_year, first_selectable_month, first_selectable_day
						+ (!$.isArray(plugin.settings.direction) ? to_int(plugin.settings.direction)
								: to_int(plugin.settings.direction[0] === false ? 0 : plugin.settings.direction[0])));
				// re-extract the date parts
				last_selectable_month = end_date.getMonth();
				last_selectable_year = end_date.getFullYear();
				last_selectable_day = end_date.getDate();
				// if an exact starting date was given, and the date is before the ending date, use that as a starting date
				if (tmp_start_date && +tmp_start_date < +end_date)
					start_date = tmp_start_date;
				// if have information about the starting date
				else if (!tmp_start_date && $.isArray(plugin.settings.direction))
					// figure out the staring date
					// use the Date object to normalize the date
					// for example, 2011 05 33 will be transformed to 2011 06 02
					start_date = new Date(last_selectable_year, last_selectable_month, last_selectable_day
							- to_int(plugin.settings.direction[1]));
				// if a valid starting date exists
				if (start_date) {
					// extract the date parts
					first_selectable_month = start_date.getMonth();
					first_selectable_year = start_date.getFullYear();
					first_selectable_day = start_date.getDate();
				}
			}
			// if a first selectable date exists but is disabled, find the actual first selectable date
			if (start_date && is_disabled(first_selectable_year, first_selectable_month, first_selectable_day)) {
				// loop until we find the first selectable year
				while (is_disabled(first_selectable_year)) {
					// if calendar is past-only, decrement the year
					if (!start_date)
						first_selectable_year--;
					// otherwise, increment the year
					else
						first_selectable_year++;
					// because we've changed years, reset the month to January
					first_selectable_month = 0;
				}
				// loop until we find the first selectable month
				while (is_disabled(first_selectable_year, first_selectable_month)) {
					// if calendar is past-only, decrement the month
					if (!start_date)
						first_selectable_month--;
					// otherwise, increment the month
					else
						first_selectable_month++;
					// if we moved to a following year
					if (first_selectable_month > 11) {
						// increment the year
						first_selectable_year++;
						// reset the month to January
						first_selectable_month = 0;
						// if we moved to a previous year
					} else if (first_selectable_month < 0) {
						// decrement the year
						first_selectable_year--;
						// reset the month to January
						first_selectable_month = 0;
					}
					// because we've changed months, reset the day to the first day of the month
					first_selectable_day = 1;
				}
				// loop until we find the first selectable day
				while (is_disabled(first_selectable_year, first_selectable_month, first_selectable_day))
					// if calendar is past-only, decrement the day
					if (!start_date)
						first_selectable_day--;
					// otherwise, increment the day
					else
						first_selectable_day++;
				// use the Date object to normalize the date
				// for example, 2011 05 33 will be transformed to 2011 06 02
				date = new Date(first_selectable_year, first_selectable_month, first_selectable_day);
				// re-extract date parts from the normalized date
				// as we use them in the current loop
				first_selectable_year = date.getFullYear();
				first_selectable_month = date.getMonth();
				first_selectable_day = date.getDate();
			}
			// parse the rules for disabling dates and turn them into arrays of arrays
			// array that will hold the rules for disabling dates
			disabled_dates = [];
			// if disabled dates is an array and is not empty
			if ($.isArray(plugin.settings.disabled_dates) && plugin.settings.disabled_dates.length > 0)
				// iterate through the rules for disabling dates
				$.each(plugin.settings.disabled_dates, function() {
					// split the values in rule by white space
					var rules = this.split(' ');
					// there can be a maximum of 4 rules (days, months, years and, optionally, day of the week)
					for ( var i = 0; i < 4; i++) {
						// if one of the values is not available
						// replace it with a * (wildcard)
						if (!rules[i])
							rules[i] = '*';
						// if rule contains a comma, create a new array by splitting the rule by commas
						// if there are no commas create an array containing the rule's string
						rules[i] = (rules[i].indexOf(',') > -1 ? rules[i].split(',') : new Array(rules[i]));
						// iterate through the items in the rule
						for ( var j = 0; j < rules[i].length; j++)
							// if item contains a dash (defining a range)
							if (rules[i][j].indexOf('-') > -1) {
								// get the lower and upper limits of the range
								var limits = rules[i][j].match(/^([0-9]+)\-([0-9]+)/);
								// if range is valid
								if (null != limits) {
									// iterate through the range
									for ( var k = to_int(limits[1]); k <= to_int(limits[2]); k++)
										// if value is not already among the values of the rule
										// add it to the rule
										if ($.inArray(k, rules[i]) == -1)
											rules[i].push(k + '');
									// remove the range indicator
									rules[i].splice(j, 1);
								}
							}
						// iterate through the items in the rule
						// and make sure that numbers are numbers
						for (j = 0; j < rules[i].length; j++)
							rules[i][j] = (isNaN(to_int(rules[i][j])) ? rules[i][j] : to_int(rules[i][j]));
					}
					// add to the list of processed rules
					disabled_dates.push(rules);
				});
			// get the default date, from the element, and check if it represents a valid date, according to the required format
			var default_date = check_date($element.val()
					|| (plugin.settings.start_date ? plugin.settings.start_date : ''));
			// if there is a default date but it is disabled
			if (default_date
					&& is_disabled(default_date.getFullYear(), default_date.getMonth(), default_date.getDate()))
				// clear the value of the parent element
				$element.val('');
			// updates value for the date picker whose starting date depends on the selected date (if any)
			update_dependent(default_date);
			// if we're just creating the date picker
			if (!update) {
				// create the calendar icon (show a disabled icon if the element is disabled)
				var html = '<button type="button" class="Zebra_DatePicker_Icon'
						+ ($element.attr('disabled') == 'disabled' ? ' Zebra_DatePicker_Icon_Disabled' : '')
						+ '">Pick a date</button>';
				// convert to a jQuery object
				icon = $(html);
				// a reference to the icon, as a global property
				plugin.icon = icon;
				// by default, only clicking the calendar icon shows the date picker
				// if text box is read-only, clicking it, will also show the date picker
				// attach the click event
				(plugin.settings.readonly_element ? icon.add($element) : icon).bind('click', function(e) {
					e.preventDefault();
					// if element is not disabled
					if (!$element.attr('disabled'))
						// if the date picker is visible, hide it
						if (datepicker.css('display') != 'none')
							plugin.hide();
						// if the date picker is not visible, show it
						else
							plugin.show();
				});
				// inject the icon into the DOM
				icon.insertAfter(element);
			}
			// if icon is to be placed *inside* the element
			// add an extra class to the icon
			if (plugin.settings.inside)
				icon.addClass('Zebra_DatePicker_Icon_Inside');
			var
			// get element's position relative to the offset parent
			element_position = $element.position(),
			// get element's width and height
			element_height = $element.outerHeight(false), element_margin_top = parseInt($element.css('marginTop'),
					10) || 0, element_width = $element.outerWidth(false), element_margin_left = parseInt($element
					.css('marginLeft'), 10) || 0,
			// get icon's width and height
			icon_width = icon.outerWidth(true), icon_height = icon.outerHeight(true);
			// if icon is to be placed *inside* the element
			if (plugin.settings.inside)
				// position the icon accordingly
				icon.css({
					'left' : element_position.left + element_margin_left + element_width - icon_width,
					'top' : element_position.top + element_margin_top + ((element_height - icon_height) / 2)
				});
			// if icon is to be placed to the right of the element
			else
				// position the icon accordingly
				icon.css({
					'left' : element_position.left + element_width,
					'top' : element_position.top + ((element_height - icon_height) / 2)
				});
			// if calendar icon exists (there's no icon if the date picker is always visible)
			if (undefined != icon)
				// if parent element is not visible (has display: none, width and height are explicitly set to 0, an ancestor
				// element is hidden, so the element is not shown on the page), hide the icon, or show it otherwise
				if (!($element.is(':visible')))
					icon.css('display', 'none');
				else
					icon.css('display', 'block');
			// if we just needed to recompute the things above, return now
			if (update)
				return;
			// generate the container that will hold everything
			var html = '' + '<div class="Zebra_DatePicker">' + 
				'<table class="dp_header">' + 
				'<tr>' + 
				'<td class="dp_previous">&laquo;</td>' + 
				'<td class="dp_caption">&nbsp;</td>' + 
				'<td class="dp_next">&raquo;</td>' + 
				'</tr>' + 
				'</table>' + 
				'<table class="dp_daypicker"></table>' + 
				'<table class="dp_monthpicker"></table>' + 
				'<table class="dp_yearpicker"></table>' +
				'<table class="dp_footer">' + 
				'<tr><td class="dp_previous"onselectstart="return false" >«</td>' + 
				'<td class="dp_blocked">12</td>' + 
				'<td class="dp_next" onselectstart="return false" >»</td>' + 
				'<td class="dp_previous"onselectstart="return false" >«</td>' + 
				'<td class="dp_blocked">00</td>' + 
				'<td class="dp_next"onselectstart="return false" >»</td>' + 
				'<td class="dp_caption">清除</td>' + 
				'<td class="dp_caption">确定</td></tr>' + 
				'</table>' + 
				'</div>';
			// create a jQuery object out of the HTML above and create a reference to it
			datepicker = $(html);
			// a reference to the calendar, as a global property
			plugin.datepicker = datepicker;
			// create references to the different parts of the date picker
			header = $('table.dp_header', datepicker);
			daypicker = $('table.dp_daypicker', datepicker);
			monthpicker = $('table.dp_monthpicker', datepicker);
			yearpicker = $('table.dp_yearpicker', datepicker);
			footer = $('table.dp_footer', datepicker);
			timepickers = footer.find('td');
			timepickers.eq(1).data('hour', 12);
			timepickers.eq(4).data('min', 0);
			// inject the container into the DOM
			$('body').append(datepicker);

				// add the mouseover/mousevents to all to the date picker's cells
			// except those that are not selectable
			datepicker.delegate(
					'td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked, .dp_week_number)',
					'mouseover', function() {
						$(this).addClass('dp_hover');
					}).delegate(
					'td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked, .dp_week_number)',
					'mouseout', function() {
						$(this).removeClass('dp_hover');
					});
			// prevent text highlighting for the text in the header
			// (for the case when user keeps clicking the "next" and "previous" buttons)
			disable_text_select($('td', header));
			// event for when clicking the "previous" button
			$('.dp_previous', header).bind('click', function() {
				// if button is not disabled
				if (!$(this).hasClass('dp_blocked')) {
					// if view is "months"
					// decrement year by one
					if (view == 'months')
						selected_year--;
					// if view is "years"
					// decrement years by 12
					else if (view == 'years')
						selected_year -= 12;
					// if view is "days"
					// decrement the month and
					// if month is out of range
					else if (--selected_month < 0) {
						// go to the last month of the previous year
						selected_month = 11;
						selected_year--;
					}
					// generate the appropriate view
					manage_views();
				}
			});
			// attach a click event to the caption in header
			$('.dp_caption', header).bind(
					'click',
					function() {
						// if current view is "days", take the user to the next view, depending on the format
						if (view == 'days')
							view = ($.inArray('months', views) > -1 ? 'months'
									: ($.inArray('years', views) > -1 ? 'years' : 'days'));
						// if current view is "months", take the user to the next view, depending on the format
						else if (view == 'months')
							view = ($.inArray('years', views) > -1 ? 'years' : ($.inArray('days', views) > -1 ? 'days'
									: 'months'));
						// if current view is "years", take the user to the next view, depending on the format
						else
							view = ($.inArray('days', views) > -1 ? 'days'
									: ($.inArray('months', views) > -1 ? 'months' : 'years'));
						// generate the appropriate view
						manage_views();
					});
			// event for when clicking the "next" button
			$('.dp_next', header).bind('click', function() {
				// if button is not disabled
				if (!$(this).hasClass('dp_blocked')) {
					// if view is "months"
					// increment year by 1
					if (view == 'months')
						selected_year++;
					// if view is "years"
					// increment years by 12
					else if (view == 'years')
						selected_year += 12;
					// if view is "days"
					// increment the month and
					// if month is out of range
					else if (++selected_month == 12) {
						// go to the first month of the next year
						selected_month = 0;
						selected_year++;
					}
					// generate the appropriate view
					manage_views();
				}
			});
			// attach a click event for the cells in the day picker
			daypicker.delegate('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_week_number)',
					'click', function() {
				//TODO
				if(plugin.settings.always_show_clear)	{
					daypicker.find(".dp_on").removeClass("dp_on");
					$(this).addClass("dp_on");
				} else {
					// put selected date in the element the plugin is attached to, and hide the date picker
					select_date(selected_year, selected_month, to_int($(this).html()), 'days');
				}
			});
			// attach a click event for the cells in the month picker
			monthpicker.delegate('td:not(.dp_disabled)', 'click', function() {
				// get the month we've clicked on
				var matches = $(this).attr('class').match(/dp\_month\_([0-9]+)/);
				// set the selected month
				selected_month = to_int(matches[1]);
				// if user can select only years and months
				if ($.inArray('days', views) == -1)
					// put selected date in the element the plugin is attached to, and hide the date picker
					select_date(selected_year, selected_month, 1, 'months');
				else {
					// direct the user to the "days" view
					view = 'days';
					// generate the appropriate view
					manage_views();
				}
			});
			// attach a click event for the cells in the year picker
			yearpicker.delegate('td:not(.dp_disabled)', 'click', function() {
				// set the selected year
				selected_year = to_int($(this).html());
				// if user can select only years
				if ($.inArray('months', views) == -1)
					// put selected date in the element the plugin is attached to, and hide the date picker
					select_date(selected_year, 1, 1, 'years');
				else {
					// direct the user to the "months" view
					view = 'months';
					// generate the appropriate view
					manage_views();
				}
			});
			//FIXME
			// bind functions to the elements in footer
			timepickers.eq(0).bind('click', function(e) {
				e.preventDefault();
				var hour = timepickers.eq(1).data('hour');
				if(hour<1) {
					hour = 24;
				}
				hour--;
				timepickers.eq(1).data('hour', hour);
				if(hour<10) {
					timepickers.eq(1).html("0"+hour);
				} else {
					timepickers.eq(1).html(hour);
				}
			});
			timepickers.eq(2).bind('click', function(e) {
				e.preventDefault();
				var hour = timepickers.eq(1).data('hour');
				if(hour>22) {
					hour=-1;
				} 
				hour++;
				timepickers.eq(1).data('hour', hour);
				if(hour<10) {
					timepickers.eq(1).html("0"+hour);
				} else {
					timepickers.eq(1).html(hour);
				}
			});
			timepickers.eq(3).bind('click', function(e) {
				e.preventDefault();
				var min = timepickers.eq(4).data('min');
				if(min<1) {
					min = 60;
				}
				min -= 5;
				timepickers.eq(4).data('min', min);
				if(min<10) {
					timepickers.eq(4).html("0"+min);
				} else {
					timepickers.eq(4).html(min);
				}
			});
			timepickers.eq(5).bind('click', function(e) {
				e.preventDefault();
				var min = timepickers.eq(4).data('min');
				if(min>54) {
					min=-5;
				} 
				min += 5;
				timepickers.eq(4).data('min', min);
				if(min<10) {
					timepickers.eq(4).html("0"+min);
				} else {
					timepickers.eq(4).html(min);
				}
			});
			timepickers.eq(6).bind('click', function(e) {
				e.preventDefault();
				$element.val('');
				plugin.hide();
			});
			timepickers.eq(7).bind('click', function(e) {
				e.preventDefault();
				var dom = daypicker.find(".dp_current").eq(0);
				if(daypicker.find(".dp_on").size()>0) {
					dom = daypicker.find(".dp_on").eq(0);
				}
				// put selected date in the element the plugin is attached to, and hide the date picker
				select_date(selected_year, selected_month, to_int(dom.html()), 'days');
			});
			
			/*
			// bind a function to the onClick event on the table cell in the footer
			$('td', footer).bind('click', function(e) {
				e.preventDefault();
				// clear the element's value
				$element.val('');
				// if date picker is not always visible
				if (!plugin.settings.always_visible) {
					// reset these values
					default_day = null;
					default_month = null;
					default_year = null;
					selected_month = null;
					selected_year = null;
					// remove the footer element
					footer.css('display', 'none');
				}
				// hide the date picker
				plugin.hide();
			});
			*/
				// bind some events to the document
				$(document).bind({
					//whenever anything is clicked on the page or a key is pressed
					'mousedown' : plugin._mousedown,
					'keyup' : plugin._keyup
				});
			// last thing is to pre-render some of the date picker right away
			manage_views();
		};
		/**
		 *  Hides the date picker.
		 *
		 *  @return void
		 */
		plugin.hide = function() {
			// hide the iFrameShim in Internet Explorer 6
			iframeShim('hide');
			// hide the date picker
			// datepicker.css('display', 'none');
			datepicker.slideUp("fast");
		};
		/**
		 *  Shows the date picker.
		 *
		 *  @return void
		 */
		plugin.show = function() {
			// always show the view defined in settings
			view = plugin.settings.view;
			// get the default date, from the element, and check if it represents a valid date, according to the required format
			var preDate = $element.val().split(" ")[0];
			var default_date = check_date(preDate
					|| (plugin.settings.start_date ? plugin.settings.start_date : ''));
			// if the value represents a valid date
			if (default_date) {
				// extract the date parts
				// we'll use these to highlight the default date in the date picker and as starting point to
				// what year and month to start the date picker with
				// why separate values? because selected_* will change as user navigates within the date picker
				default_month = default_date.getMonth();
				selected_month = default_date.getMonth();
				default_year = default_date.getFullYear();
				selected_year = default_date.getFullYear();
				default_day = default_date.getDate();
				// if the default date represents a disabled date
				if (is_disabled(default_year, default_month, default_day)) {
					// clear the value of the parent element
					$element.val('');
					// the calendar will start with the first selectable year/month
					selected_month = first_selectable_month;
					selected_year = first_selectable_year;
				} 
				// if a default value is not available, or value does not represent a valid date
			} else {
				// the calendar will start with the first selectable year/month
				selected_month = first_selectable_month;
				selected_year = first_selectable_year;
			}
			// generate the appropriate view
			manage_views();
			var
			// get the date picker width and height
			datepicker_width = datepicker.outerWidth(), datepicker_height = datepicker.outerHeight(),
			// compute the date picker's default left and top
			offset = $element.offset(), left = offset.left, top = offset.top + $element.outerHeight(true) + 1,
			// get browser window's width and height
			window_width = $(window).width(), window_height = $(window).height(),
			// get browser window's horizontal and vertical scroll offsets
			window_scroll_top = $(window).scrollTop(), window_scroll_left = $(window).scrollLeft();
			// if date picker is outside the viewport, adjust its position so that it is visible
			if (left + datepicker_width > window_scroll_left + window_width)
				left = window_scroll_left + window_width - datepicker_width;
			if (left < window_scroll_left)
				left = window_scroll_left;
			if (top + datepicker_height > window_scroll_top + window_height)
				top = window_scroll_top + window_height - datepicker_height;
			if (top < window_scroll_top)
				top = window_scroll_top;
			// make the date picker visible
			datepicker.css({
				'left' : left,
				'top' : top,
				'z-index' : plugin.settings.zIndex
			});
			// fade-in the date picker
			// for Internet Explorer < 9 show the date picker instantly or fading alters the font's weight
			// datepicker.fadeIn($.browser.msie && $.browser.version.match(/^[6-8]/) ? 0 : 150, 'linear');
			datepicker.slideDown("fast");
			// show the iFrameShim in Internet Explorer 6
			iframeShim();
		};
		/**
		 *  Updates the configuration options given as argument
		 *
		 *  @param  object  values  An object containing any number of configuration options to be updated
		 *
		 *  @return void
		 */
		plugin.update = function(values) {
			// if original direction not saved, save it now
			if (plugin.original_direction)
				plugin.original_direction = plugin.direction;
			// update configuration options
			plugin.settings = $.extend(plugin.settings, values);
			// re-initialize the object with the new options
			init(true);
		};
		/**
		 *  Checks if a string represents a valid date according to the format defined by the "format" property.
		 *
		 *  @param  string  str_date    A string representing a date, formatted accordingly to the "format" property.
		 *                              For example, if "format" is "Y-m-d" the string should look like "2011-06-01"
		 *
		 *  @return boolean             Returns TRUE if string represents a valid date according formatted according to
		 *                              the "format" property or FALSE otherwise.
		 *
		 *  @access private
		 */
		var check_date = function(str_date) {
			// treat argument as a string
			str_date += '';
			// if value is given
			if ($.trim(str_date) != '') {
				var
				// prepare the format by removing white space from it
				// and also escape characters that could have special meaning in a regular expression
				format = escape_regexp(plugin.settings.format.replace(/\s/g, '')),
				// allowed characters in date's format
				format_chars = [ 'd', 'D', 'j', 'l', 'N', 'S', 'w', 'F', 'm', 'M', 'n', 'Y', 'y' ],
				// "matches" will contain the characters defining the date's format
				matches = new Array,
				// "regexp" will contain the regular expression built for each of the characters used in the date's format
				regexp = new Array;
				// iterate through the allowed characters in date's format
				for ( var i = 0; i < format_chars.length; i++)
					// if character is found in the date's format
					if ((position = format.indexOf(format_chars[i])) > -1)
						// save it, alongside the character's position
						matches.push({
							character : format_chars[i],
							position : position
						});
				// sort characters defining the date's format based on their position, ascending
				matches.sort(function(a, b) {
					return a.position - b.position;
				});
				// iterate through the characters defining the date's format
				$.each(matches, function(index, match) {
					// add to the array of regular expressions, based on the character
					switch (match.character) {
					case 'd':
						regexp.push('0[1-9]|[12][0-9]|3[01]');
						break;
					case 'D':
						regexp.push('[a-z]{3}');
						break;
					case 'j':
						regexp.push('[1-9]|[12][0-9]|3[01]');
						break;
					case 'l':
						regexp.push('[a-z]+');
						break;
					case 'N':
						regexp.push('[1-7]');
						break;
					case 'S':
						regexp.push('st|nd|rd|th');
						break;
					case 'w':
						regexp.push('[0-6]');
						break;
					case 'F':
						regexp.push('[a-z]+');
						break;
					case 'm':
						regexp.push('0[1-9]|1[012]+');
						break;
					case 'M':
						regexp.push('[a-z]{3}');
						break;
					case 'n':
						regexp.push('[1-9]|1[012]');
						break;
					case 'Y':
						regexp.push('[0-9]{4}');
						break;
					case 'y':
						regexp.push('[0-9]{2}');
						break;
					}
				});
				// if we have an array of regular expressions
				if (regexp.length) {
					// we will replace characters in the date's format in reversed order
					matches.reverse();
					// iterate through the characters in date's format
					$.each(matches, function(index, match) {
						// replace each character with the appropriate regular expression
						format = format.replace(match.character, '(' + regexp[regexp.length - index - 1] + ')');
					});
					// the final regular expression
					regexp = new RegExp('^' + format + '$', 'ig');
					// if regular expression was matched
					if ((segments = regexp.exec(str_date.replace(/\s/g, '')))) {
						// check if date is a valid date (i.e. there's no February 31)
						var original_day, original_month, original_year, english_days = [ 'Sunday', 'Monday',
								'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], english_months = [
								'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September',
								'October', 'November', 'December' ], iterable,
						// by default, we assume the date is valid
						valid = true;
						// reverse back the characters in the date's format
						matches.reverse();
						// iterate through the characters in the date's format
						$.each(matches, function(index, match) {
							// if the date is not valid, don't look further
							if (!valid)
								return true;
							// based on the character
							switch (match.character) {
							case 'm':
							case 'n':
								// extract the month from the value entered by the user
								original_month = to_int(segments[index + 1]);
								break;
							case 'd':
							case 'j':
								// extract the day from the value entered by the user
								original_day = to_int(segments[index + 1]);
								break;
							case 'D':
							case 'l':
							case 'F':
							case 'M':
								// if day is given as day name, we'll check against the names in the used language
								if (match.character == 'D' || match.character == 'l')
									iterable = plugin.settings.days;
								// if month is given as month name, we'll check against the names in the used language
								else
									iterable = plugin.settings.months;
								// by default, we assume the day or month was not entered correctly
								valid = false;
								// iterate through the month/days in the used language
								$.each(iterable, function(key, value) {
									// if month/day was entered correctly, don't look further
									if (valid)
										return true;
									// if month/day was entered correctly
									if (segments[index + 1].toLowerCase() == value.substring(0,
											(match.character == 'D' || match.character == 'M' ? 3 : value.length))
											.toLowerCase()) {
										// extract the day/month from the value entered by the user
										switch (match.character) {
										case 'D':
											segments[index + 1] = english_days[key].substring(0, 3);
											break;
										case 'l':
											segments[index + 1] = english_days[key];
											break;
										case 'F':
											segments[index + 1] = english_months[key];
											original_month = key + 1;
											break;
										case 'M':
											segments[index + 1] = english_months[key].substring(0, 3);
											original_month = key + 1;
											break;
										}
										// day/month value is valid
										valid = true;
									}
								});
								break;
							case 'Y':
								// extract the year from the value entered by the user
								original_year = to_int(segments[index + 1]);
								break;
							case 'y':
								// extract the year from the value entered by the user
								original_year = '19' + to_int(segments[index + 1]);
								break;
							}
						});
						// if everything is ok so far
						if (valid) {
							// generate a Date object using the values entered by the user
							// (handle also the case when original_month and/or original_day are undefined - i.e date format is "Y-m" or "Y")
							var date = new Date(original_year, (original_month || 1) - 1, original_day || 1);
							// if, after that, the date is the same as the date entered by the user
							if (date.getFullYear() == original_year && date.getDate() == (original_day || 1)
									&& date.getMonth() == ((original_month || 1) - 1))
								// return the date as JavaScript date object
								return date;
						}
					}
				}
				// if script gets this far, return false as something must've went wrong
				return false;
			}
		};
		/**
		 *  Prevents the possibility of selecting text on a given element. Used on the "previous" and "next" buttons
		 *  where text might get accidentally selected when user quickly clicks on the buttons.
		 *
		 *  Code by http://chris-barr.com/index.php/entry/disable_text_selection_with_jquery/
		 *
		 *  @param  jQuery Element  el  A jQuery element on which to prevents text selection.
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var disable_text_select = function(el) {
			// if browser is Firefox
			if ($.browser.mozilla)
				el.css('MozUserSelect', 'none');
			// if browser is Internet Explorer
			else if ($.browser.msie)
				el.bind('selectstart', function() {
					return false;
				});
			// for the other browsers
			else
				el.mousedown(function() {
					return false;
				});
		};
		/**
		 *  Escapes special characters in a string, preparing it for use in a regular expression.
		 *
		 *  @param  string  str     The string in which special characters should be escaped.
		 *
		 *  @return string          Returns the string with escaped special characters.
		 *
		 *  @access private
		 */
		var escape_regexp = function(str) {
			// return string with special characters escaped
			return str.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
		};
		/**
		 *  Formats a JavaScript date object to the format specified by the "format" property.
		 *  Code taken from http://electricprism.com/aeron/calendar/
		 *
		 *  @param  date    date    A valid JavaScript date object
		 *
		 *  @return string          Returns a string containing the formatted date
		 *
		 *  @access private
		 */
		var format = function(date) {
			var result = '',
			// extract parts of the date:
			// day number, 1 - 31
			j = date.getDate(),
			// day of the week, 0 - 6, Sunday - Saturday
			w = date.getDay(),
			// the name of the day of the week Sunday - Saturday
			l = plugin.settings.days[w],
			// the month number, 1 - 12
			n = date.getMonth() + 1,
			// the month name, January - December
			f = plugin.settings.months[n - 1],
			// the year (as a string)
			y = date.getFullYear() + '';
			// iterate through the characters in the format
			for ( var i = 0; i < plugin.settings.format.length; i++) {
				// extract the current character
				var chr = plugin.settings.format.charAt(i);
				// see what character it is
				switch (chr) {
				// year as two digits
				case 'y':
					y = y.substr(2);
					// year as four digits
				case 'Y':
					result += y;
					break;
				// month number, prefixed with 0
				case 'm':
					n = str_pad(n, 2);
					// month number, not prefixed with 0
				case 'n':
					result += n;
					break;
				// month name, three letters
				case 'M':
					f = f.substr(0, 3);
					// full month name
				case 'F':
					result += f;
					break;
				// day number, prefixed with 0
				case 'd':
					j = str_pad(j, 2);
					// day number not prefixed with 0
				case 'j':
					result += j;
					break;
				// day name, three letters
				case 'D':
					l = l.substr(0, 3);
					// full day name
				case 'l':
					result += l;
					break;
				// ISO-8601 numeric representation of the day of the week, 1 - 7
				case 'N':
					w++;
					// day of the week, 0 - 6
				case 'w':
					result += w;
					break;
				// English ordinal suffix for the day of the month, 2 characters
				// (st, nd, rd or th (works well with j))
				case 'S':
					if (j % 10 == 1 && j != '11')
						result += 'st';
					else if (j % 10 == 2 && j != '12')
						result += 'nd';
					else if (j % 10 == 3 && j != '13')
						result += 'rd';
					else
						result += 'th';
					break;
				// this is probably the separator
				default:
					result += chr;
				}
			}
			// return formated date
			return result;
		};
		/**
		 *  Generates the day picker view, and displays it
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var generate_daypicker = function() {
			var
			// get the number of days in the selected month
			days_in_month = new Date(selected_year, selected_month + 1, 0).getDate(),
			// get the selected month's starting day (from 0 to 6)
			first_day = new Date(selected_year, selected_month, 1).getDay(),
			// how many days are there in the previous month
			days_in_previous_month = new Date(selected_year, selected_month, 0).getDate(),
			// how many days are there to be shown from the previous month
			days_from_previous_month = first_day - plugin.settings.first_day_of_week;
			// the final value of how many days are there to be shown from the previous month
			days_from_previous_month = days_from_previous_month < 0 ? 7 + days_from_previous_month
					: days_from_previous_month;
			// manage header caption and enable/disable navigation buttons if necessary
			manage_header(plugin.settings.showmonths[selected_month] + ', ' + selected_year);
			// start generating the HTML
			var html = '<tr>';
			// if a column featuring the number of the week is to be shown
			if (plugin.settings.show_week_number)
				// column title
				html += '<th>' + plugin.settings.show_week_number + '</th>';
			// name of week days
			// show only the first two letters
			// and also, take in account the value of the "first_day_of_week" property
			for ( var i = 0; i < 7; i++)
				html += '<th>' + plugin.settings.showdays[(plugin.settings.first_day_of_week + i) % 7] + '</th>';
			html += '</tr><tr>';
			// the calendar shows a total of 42 days
			for ( var i = 0; i < 42; i++) {
				// seven days per row
				if (i > 0 && i % 7 == 0)
					html += '</tr><tr>';
				// if week number is to be shown
				if (i % 7 == 0 && plugin.settings.show_week_number) {
					// get the ISO 8601 week number
					// code taken from http://www.epoch-calendar.com/support/getting_iso_week.html
					var
					// current normalized date
					current_date = new Date(selected_year, selected_month, (i - days_from_previous_month + 1)),
					// create a new date object representing january 1st of the currently selected year
					year_start_date = new Date(selected_year, 0, 1),
					// the day of week day the year begins with (0 to 6)
					// (taking locale into account)
					start_weekday = year_start_date.getDay() - plugin.settings.first_day_of_week,
					// the number of the current day
					current_day_number = Math.floor((current_date.getTime() - year_start_date.getTime() - (current_date
							.getTimezoneOffset() - year_start_date.getTimezoneOffset()) * 60000) / 86400000) + 1,
					// this will be the current week number
					week_number;
					// normalize starting day of the year in case it is < 0
					start_weekday = (start_weekday >= 0 ? start_weekday : start_weekday + 7);
					//if the year starts before the middle of a week
					if (start_weekday < 4) {
						// get the week's number
						week_number = Math.floor((current_day_number + start_weekday - 1) / 7) + 1;
						// if week is > 52 then we have to figure out if it is the 53rd week of the current year
						// or the 1st week of the next year
						if (week_number + 1 > 52) {
							var
							// create a date object represnting january 1st of the next year
							// tmp_year = new Date(current_date.getFullYear() + 1, 0, 1),
							// the day of week day the year begins with (0 to 6)
							// (taking locale into account)
							tmp_day = nYear.getDay() - plugin.settings.first_day_of_week;
							// normalize starting day of the year in case it is < 0
							tmp_day = (tmp_day >= 0 ? tmp_day : tmp_day + 7);
							// if the next year starts before the middle of the week,
							// the week number represents the 1st week of that year
							week_number = (tmp_day < 4 ? 1 : 53);
						}
						// otherwise, this is the week's number
					} else {
						week_number = Math.floor((current_day_number + start_weekday - 1) / 7);
					}
					// add week number
					html += '<td class="dp_week_number">' + week_number + '</td>';
				}
				// the number of the day in month
				var day = (i - days_from_previous_month + 1);
				// if this is a day from the previous month
				if (i < days_from_previous_month)
					html += '<td class="dp_not_in_month">'
							+ (days_in_previous_month - days_from_previous_month + i + 1) + '</td>';
				// if this is a day from the next month
				else if (day > days_in_month)
					html += '<td class="dp_not_in_month">' + (day - days_in_month) + '</td>';
				// if this is a day from the current month
				else {
					var
					// get the week day (0 to 6, Sunday to Saturday)
					weekday = (plugin.settings.first_day_of_week + i) % 7, class_name = '';
					// if date needs to be disabled
					if (is_disabled(selected_year, selected_month, day)) {
						// if day is in weekend
						if ($.inArray(weekday, plugin.settings.weekend_days) > -1)
							class_name = 'dp_weekend_disabled';
						// if work day
						else
							class_name += ' dp_disabled';
						// highlight the current system date
						if (selected_month == current_system_month && selected_year == current_system_year
								&& current_system_day == day)
							class_name += ' dp_disabled_current';
						// if there are no restrictions
					} else {
						// if day is in weekend
						if ($.inArray(weekday, plugin.settings.weekend_days) > -1)
							class_name = 'dp_weekend';
						// highlight the currently selected date
						if (selected_month == default_month && selected_year == default_year && default_day == day)
							class_name += ' dp_on';
						// highlight the current system date
						if (selected_month == current_system_month && selected_year == current_system_year
								&& current_system_day == day)
							class_name += ' dp_current';
					}
					// print the day of the month
					html += '<td' + (class_name != '' ? ' class="' + $.trim(class_name) + '"' : '') + '>'
							+ str_pad(day, 2) + '</td>';
				}
			}
			// wrap up generating the day picker
			html += '</tr>';
			// inject the day picker into the DOM
			daypicker.html($(html));
			// make the day picker visible
			daypicker.css('display', '');
			// value the hour and minute toolkit
			if(plugin.settings.always_show_clear) {
				var preTime = $element.val().split(" ")[1];
				if(preTime) {
					var _hour = preTime.split(":")[0];
					var _min = preTime.split(":")[1];
					timepickers.eq(1).html(_hour).data("hour", parseInt(_hour, 10));
					timepickers.eq(4).html(_min).data("min", parseInt(_min, 10));
				}
			}
		};
		/**
		 *  Generates the month picker view, and displays it
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var generate_monthpicker = function() {
			// manage header caption and enable/disable navigation buttons if necessary
			manage_header(selected_year);
			// start generating the HTML
			var html = '<tr>';
			// iterate through all the months
			for ( var i = 0; i < 12; i++) {
				// three month per row
				if (i > 0 && i % 3 == 0)
					html += '</tr><tr>';
				var class_name = 'dp_month_' + i;
				// if month needs to be disabled
				if (is_disabled(selected_year, i))
					class_name += ' dp_disabled';
				// else, if a date is already selected and this is that particular month, highlight it
				else if (default_month !== false && default_month == i)
					class_name += ' dp_selected';
				// else, if this the current system month, highlight it
				else if (current_system_month == i && current_system_year == selected_year)
					class_name += ' dp_current';
				// first three letters of the month's name
				html += '<td class="' + $.trim(class_name) + '">' + plugin.settings.showmonths[i] + '</td>';
			}
			// wrap up
			html += '</tr>';
			// inject into the DOM
			monthpicker.html($(html));
			// make the month picker visible
			monthpicker.css('display', '');
		};
		/**
		 *  Generates the year picker view, and displays it
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var generate_yearpicker = function() {
			// manage header caption and enable/disable navigation buttons if necessary
			manage_header(selected_year - 7 + ' - ' + (selected_year + 4));
			// start generating the HTML
			var html = '<tr>';
			// we're showing 9 years at a time, current year in the middle
			for ( var i = 0; i < 12; i++) {
				// three years per row
				if (i > 0 && i % 3 == 0)
					html += '</tr><tr>';
				var class_name = '';
				// if year needs to be disabled
				if (is_disabled(selected_year - 7 + i))
					class_name += ' dp_disabled';
				// else, if a date is already selected and this is that particular year, highlight it
				else if (default_year && default_year == selected_year - 7 + i)
					class_name += ' dp_selected';
				// else, if this is the current system year, highlight it
				else if (current_system_year == (selected_year - 7 + i))
					class_name += ' dp_current';
				// first three letters of the month's name
				html += '<td' + ($.trim(class_name) != '' ? ' class="' + $.trim(class_name) + '"' : '') + '>'
						+ (selected_year - 7 + i) + '</td>';
			}
			// wrap up
			html += '</tr>';
			// inject into the DOM
			yearpicker.html($(html));
			// make the year picker visible
			yearpicker.css('display', '');
		};
		/**
		 *  Generates an iFrame shim in Internet Explorer 6 so that the date picker appears above select boxes.
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var iframeShim = function(action) {
			// this is necessary only if browser is Internet Explorer 6
			if ($.browser.msie && $.browser.version.match(/^6/)) {
				// if the iFrame was not yet created
				// "undefined" evaluates as FALSE
				if (!shim) {
					// the iFrame has to have the element's zIndex minus 1
					var zIndex = to_int(datepicker.css('zIndex')) - 1;
					// create the iFrame
					shim = jQuery('<iframe>', {
						'src' : 'javascript:document.write("")',
						'scrolling' : 'no',
						'frameborder' : 0,
						'allowtransparency' : 'true',
						css : {
							'zIndex' : zIndex,
							'position' : 'absolute',
							'top' : -1000,
							'left' : -1000,
							'width' : datepicker.outerWidth(),
							'height' : datepicker.outerHeight(),
							'filter' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)',
							'display' : 'none'
						}
					});
					// inject iFrame into DOM
					$('body').append(shim);
				}
				// what do we need to do
				switch (action) {
				// hide the iFrame?
				case 'hide':
					// set the iFrame's display property to "none"
					// shim.css('display', 'none');
					shim.slideUp("fast");
					break;
				// show the iFrame?
				default:
					// get date picker top and left position
					var offset = datepicker.offset();
					// position the iFrame shim right underneath the date picker
					// and set its display to "block"
					shim.css({
						'top' : offset.top,
						'left' : offset.left
					}).slideDown("fast");
				}
			}
		};
		/**
		 *  Checks if, according to the restrictions of the calendar and/or the values defined by the "disabled_dates"
		 *  property, a day, a month or a year needs to be disabled.
		 *
		 *  @param  integer     year    The year to check
		 *  @param  integer     month   The month to check
		 *  @param  integer     day     The day to check
		 *
		 *  @return boolean         Returns TRUE if the given value is not disabled or FALSE otherwise
		 *
		 *  @access private
		 */
		var is_disabled = function(year, month, day) {
			// if calendar has direction restrictions
			if (!(!$.isArray(plugin.settings.direction) && to_int(plugin.settings.direction) === 0)) {
				var
				// normalize and merge arguments then transform the result to an integer
				now = to_int(str_concat(year, (typeof month != 'undefined' ? str_pad(month, 2) : ''),
						(typeof day != 'undefined' ? str_pad(day, 2) : ''))),
				// get the length of the argument
				len = (now + '').length;
				// if we're checking days
				if (len == 8
						&& (
						// day is before the first selectable date
						(typeof start_date != 'undefined' && now < to_int(str_concat(first_selectable_year, str_pad(
								first_selectable_month, 2), str_pad(first_selectable_day, 2)))) ||
						// or day is after the last selectable date
						(typeof end_date != 'undefined' && now > to_int(str_concat(last_selectable_year, str_pad(
								last_selectable_month, 2), str_pad(last_selectable_day, 2))))
						// day needs to be disabled
						))
					return true;
				// if we're checking months
				else if (len == 6
						&& (
						// month is before the first selectable month
						(typeof start_date != 'undefined' && now < to_int(str_concat(first_selectable_year, str_pad(
								first_selectable_month, 2)))) ||
						// or day is after the last selectable date
						(typeof end_date != 'undefined' && now > to_int(str_concat(last_selectable_year, str_pad(
								last_selectable_month, 2))))
						// month needs to be disabled
						))
					return true;
				// if we're checking years
				else if (len == 4 && (
				// year is before the first selectable year
				(typeof start_date != 'undefined' && now < first_selectable_year) ||
				// or day is after the last selectable date
				(typeof end_date != 'undefined' && now > last_selectable_year)
				// year needs to be disabled
				))
					return true;
			}
			// if there are rules for disabling dates
			if (disabled_dates) {
				// if month is given as argument, increment it (as JavaScript uses 0 for January, 1 for February...)
				if (typeof month != 'undefined')
					month = month + 1;
				// by default, we assume the day/month/year is not to be disabled
				var disabled = false;
				// iterate through the rules for disabling dates
				$.each(disabled_dates, function() {
					// if the date is to be disabled, don't look any further
					if (disabled)
						return;
					var rule = this;
					// if the rules apply for the current year
					if ($.inArray(year, rule[2]) > -1 || $.inArray('*', rule[2]) > -1)
						// if the rules apply for the current month
						if ((typeof month != 'undefined' && $.inArray(month, rule[1]) > -1)
								|| $.inArray('*', rule[1]) > -1)
							// if the rules apply for the current day
							if ((typeof day != 'undefined' && $.inArray(day, rule[0]) > -1)
									|| $.inArray('*', rule[0]) > -1) {
								// if day is to be disabled whatever the day
								// don't look any further
								if (rule[3] == '*')
									return (disabled = true);
								// get the weekday
								var weekday = new Date(year, month - 1, day).getDay();
								// if weekday is to be disabled
								// don't look any further
								if ($.inArray(weekday, rule[3]) > -1)
									return (disabled = true);
							}
				});
				// if the day/month/year needs to be disabled
				if (disabled)
					return true;
			}
			// if script gets this far it means that the day/month/year doesn't need to be disabled
			return false;
		};
		/**
		 *  Checks whether a value is an integer number.
		 *
		 *  @param  mixed   value   Value to check
		 *
		 *  @return                 Returns TRUE if the value represents an integer number, or FALSE otherwise
		 *
		 *  @access private
		 */
		var is_integer = function(value) {
			// return TRUE if value represents an integer number, or FALSE otherwise
			return (value + '').match(/^\-?[0-9]+$/) ? true : false;
		};
		/**
		 *  Sets the caption in the header of the date picker and enables or disables navigation buttons when necessary.
		 *
		 *  @param  string  caption     String that needs to be displayed in the header
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var manage_header = function(caption) {
			// update the caption in the header
			$('.dp_caption', header).html(caption);
			// if calendar has direction restrictions
			if (!(!$.isArray(plugin.settings.direction) && to_int(plugin.settings.direction) === 0)) {
				// get the current year and month
				var year = selected_year, month = selected_month, next, previous;
				// if current view is showing days
				if (view == 'days') {
					// clicking on "previous" should take us to the previous month
					// (will check later if that particular month is available)
					previous = (month - 1 < 0 ? str_concat(year - 1, '11') : str_concat(year, str_pad(month - 1, 2)));
					// clicking on "next" should take us to the next month
					// (will check later if that particular month is available)
					next = (month + 1 > 11 ? str_concat(year + 1, '00') : str_concat(year, str_pad(month + 1, 2)));
					// if current view is showing months
				} else if (view == 'months') {
					// clicking on "previous" should take us to the previous year
					// (will check later if that particular year is available)
					previous = year - 1;
					// clicking on "next" should take us to the next year
					// (will check later if that particular year is available)
					next = year + 1;
					// if current view is showing years
				} else if (view == 'years') {
					// clicking on "previous" should show a list with some previous years
					// (will check later if that particular list of years contains selectable years)
					previous = year - 7;
					// clicking on "next" should show a list with some following years
					// (will check later if that particular list of years contains selectable years)
					next = year + 7;
				}
				// if the previous month/year is not selectable or, in case of years, if the list doesn't contain selectable years
				if (is_disabled(previous)) {
					// disable the "previous" button
					$('.dp_previous', header).addClass('dp_blocked');
					$('.dp_previous', header).removeClass('dp_hover');
					// otherwise enable the "previous" button
				} else
					$('.dp_previous', header).removeClass('dp_blocked');
				// if the next month/year is not selectable or, in case of years, if the list doesn't contain selectable years
				if (is_disabled(next)) {
					// disable the "next" button
					$('.dp_next', header).addClass('dp_blocked');
					$('.dp_next', header).removeClass('dp_hover');
					// otherwise enable the "next" button
				} else
					$('.dp_next', header).removeClass('dp_blocked');
			}
		};
		/**
		 *  Shows the appropriate view (days, months or years) according to the current value of the "view" property.
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var manage_views = function() {
			// if the day picker was not yet generated
			if (daypicker.text() == '' || view == 'days') {
				// if the day picker was not yet generated
				if (daypicker.text() == '') {
					// temporarily set the date picker's left outside of view
					// so that we can later grab its width and height
					datepicker.css('left', -1000);
					// temporarily make the date picker visible
					// so that we can later grab its width and height
					datepicker.css({
						'display' : 'block'
					});
					// generate the day picker
					generate_daypicker();
					// get the day picker's width and height
					var width = daypicker.outerWidth(), height = daypicker.outerHeight();
					// adjust the size of the header
					header.css('width', width);
					// make the month picker have the same size as the day picker
					monthpicker.css({
						'width' : width,
						'height' : height
					});
					// make the year picker have the same size as the day picker
					yearpicker.css({
						'width' : width,
						'height' : height
					});
					// adjust the size of the footer
					footer.css('width', width);
					// hide the date picker again
					datepicker.css({
						'display' : 'none'
					});
					// if the day picker was previously generated at least once
					// generate the day picker
				} else
					generate_daypicker();
				// hide the year and the month pickers
				monthpicker.css('display', 'none');
				yearpicker.css('display', 'none');
				// if the view is "months"
			} else if (view == 'months') {
				// generate the month picker
				generate_monthpicker();
				// hide the day and the year pickers
				daypicker.css('display', 'none');
				yearpicker.css('display', 'none');
				// if the view is "years"
			} else if (view == 'years') {
				// generate the year picker
				generate_yearpicker();
				// hide the day and the month pickers
				daypicker.css('display', 'none');
				monthpicker.css('display', 'none');
			}
			// if a callback function exists for when navigating through months/years
			if (plugin.settings.onChange && typeof plugin.settings.onChange == 'function' && undefined != view) {
				// get the "active" elements in the view (ignoring the disabled ones)
				var elements = (view == 'days' ? daypicker
						.find('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked)')
						: (view == 'months' ? monthpicker
								.find('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked)')
								: yearpicker
										.find('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_blocked)')));
				// iterate through the active elements
				// and attach a "date" data attribute to each element in the form of
				// YYYY-MM-DD if the view is "days"
				// YYYY-MM if the view is "months"
				// YYYY if the view is "years"
				// so it's easy to identify elements in the list
				elements.each(function() {
					// if view is "days"
					if (view == 'days')
						// attach a "date" data attribute to each element in the form of of YYYY-MM-DD for easily identifying sought elements
						$(this).data(
								'date',
								selected_year + '-' + str_pad(selected_month + 1, 2) + '-'
										+ str_pad(to_int($(this).text()), 2));
					// if view is "months"
					else if (view == 'months') {
						// get the month's number for the element's class
						var matches = $(this).attr('class').match(/dp\_month\_([0-9]+)/);
						// attach a "date" data attribute to each element in the form of of YYYY-MM for easily identifying sought elements
						$(this).data('date', selected_year + '-' + str_pad(to_int(matches[1]) + 1, 2));
						// if view is "years"
					} else
						// attach a "date" data attribute to each element in the form of of YYYY for easily identifying sought elements
						$(this).data('date', to_int($(this).text()));
				});
				// execute the callback function and send as arguments the current view and the elements in the view
				plugin.settings.onChange(view, elements);
			}
			// if the button for clearing a previously selected date needs to be visible all the time,
			// or the date picker is always visible - case in which the "clear" button is always visible -
			// or there is content in the element the date picker is attached to
			// and the footer is not visible
			if ((plugin.settings.always_show_clear || $element.val() != '')
					&& footer.css('display') != 'block')
				// show the footer
				footer.css('display', '');
			// hide the footer otherwise
			else
				footer.css('display', 'none');
		};
		/**
		 *  Puts the specified date in the element the plugin is attached to, and hides the date picker.
		 *
		 *  @param  integer     year    The year
		 *
		 *  @param  integer     month   The month
		 *
		 *  @param  integer     day     The day
		 *
		 *  @param  string      view    The view from where the method was called
		 *
		 *  @param  object      cell    The element that was clicked
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var select_date = function(year, month, day, view) {
			var
			// construct a new date object from the arguments
			default_date = new Date(year, month, day),
			// the selected date, formatted correctly
			selected_value = format(default_date);
			// set the currently selected and formated date as the value of the element the plugin is attached to
			if(view=='days' && plugin.settings.always_show_clear) {
				$element.val(selected_value+" "+timepickers.eq(1).html()+":"+timepickers.eq(4).html());
			} else {
				$element.val(selected_value);
			}
			// hide the date picker
			plugin.hide();
			// updates value for the date picker whose starting date depends on the selected date (if any)
			update_dependent(default_date);
			// if a callback function exists for when selecting a date
			if (plugin.settings.onSelect && typeof plugin.settings.onSelect == 'function')
				// execute the callback function
				plugin.settings.onSelect(selected_value, year + '-' + str_pad(month + 1, 2) + '-' + str_pad(day, 2),
						new Date(year, month, day));
		};
		/**
		 *  Concatenates any number of arguments and returns them as string.
		 *
		 *  @return string  Returns the concatenated values.
		 *
		 *  @access private
		 */
		var str_concat = function() {
			var str = '';
			// concatenate as string
			for ( var i = 0; i < arguments.length; i++)
				str += (arguments[i] + '');
			// return the concatenated values
			return str;
		};
		/**
		 *  Left-pad a string to a certain length with zeroes.
		 *
		 *  @param  string  str     The string to be padded.
		 *
		 *  @param  integer len     The length to which the string must be padded
		 *
		 *  @return string          Returns the string left-padded with leading zeroes
		 *
		 *  @access private
		 */
		var str_pad = function(str, len) {
			// make sure argument is a string
			str += '';
			// pad with leading zeroes until we get to the desired length
			while (str.length < len)
				str = '0' + str;
			// return padded string
			return str;
		};
		/**
		 *  Returns the integer representation of a string
		 *
		 *  @return int     Returns the integer representation of the string given as argument
		 *
		 *  @access private
		 */
		var to_int = function(str) {
			// return the integer representation of the string given as argument
			return parseInt(str, 10);
		};
		/**
		 *  Updates the paired date picker (whose starting date depends on the value of the current date picker)
		 *
		 *  @param  date    date    A JavaScript date object representing the currently selected date
		 *
		 *  @return void
		 *
		 *  @access private
		 */
		var update_dependent = function(date) {
			// if the pair element exists
			if (plugin.settings.pair) {
				// chances are that at the beginning the pair element doesn't have the Zebra_DatePicker attached to it yet
				// (as the "start" element is usually created before the "end" element)
				// so we'll have to rely on "data" to send the starting date to the pair element
				// therefore, if Zebra_DatePicker is not yet attached
				if (!(plugin.settings.pair.data && plugin.settings.pair.data('Zebra_DatePicker')))
					// set the starting date like this
					plugin.settings.pair.data('zdp_reference_date', date);
				// if Zebra_DatePicker is attached to the pair element
				else {
					// reference the date picker object attached to the other element
					var dp = plugin.settings.pair.data('Zebra_DatePicker');
					// update the other date picker's starting date
					// the value depends on the original value of the "direction" attribute
					dp.update({
						'reference_date' : date
					});
				}
			}
		};
		/**
		 *  Function to be called when the "onKeyUp" event occurs
		 *
		 *  Why as a separate function and not inline when binding the event? Because only this way we can "unbind" it
		 *  if the date picker is destroyed
		 *
		 *  @return boolean     Returns TRUE
		 *
		 *  @access private
		 */
		plugin._keyup = function(e) {
			// if the date picker is visible
			// and the pressed key is ESC
			// hide the date picker
			if (datepicker.css('display') == 'block' || e.which == 27)
				plugin.hide();
			return true;
		};
		/**
		 *  Function to be called when the "onMouseDown" event occurs
		 *
		 *  Why as a separate function and not inline when binding the event? Because only this way we can "unbind" it
		 *  if the date picker is destroyed
		 *
		 *  @return boolean     Returns TRUE
		 *
		 *  @access private
		 */
		plugin._mousedown = function(e) {
			// if the date picker is visible
			if (datepicker.css('display') == 'block') {
				// if we clicked the date picker's icon, let the onClick event of the icon to handle the event
				// (we want it to toggle the date picker)
				if ($(e.target).get(0) === icon.get(0))
					return true;
				// if what's clicked is not inside the date picker
				// hide the date picker
				if ($(e.target).parents().filter('.Zebra_DatePicker').length == 0)
					plugin.hide();
			}
			return true;
		};
		// initialize the plugin
		init();
	};
	$.fn.Zebra_DatePicker = function(options) {
		return this.each(function() {
			// if element has a date picker already attached
			if (undefined != $(this).data('Zebra_DatePicker')) {
				// get reference to the previously attached date picker
				var plugin = $(this).data('Zebra_DatePicker');
				// remove the attached icon and calendar
				plugin.icon.remove();
				plugin.datepicker.remove();
				// remove associated event handlers from the document
				$(document).unbind('keyup', plugin._keyup);
				$(document).unbind('mousedown', plugin._mousedown);
			}
			// create a new instance of the plugin
			var plugin = new $.Zebra_DatePicker(this, options);
			// save a reference to the newly created object
			$(this).data('Zebra_DatePicker', plugin);
		});
	};
	$.fn.Zebra_DatePicker_Remove = function() {
		// if element has a date picker already attached
		if (undefined != $(this).data('Zebra_DatePicker')) {
			// get reference to the previously attached date picker
			var plugin = $(this).data('Zebra_DatePicker');
			// remove the attached icon and calendar
			plugin.icon.remove();
			plugin.datepicker.remove();
			// remove associated event handlers from the document
			$(document).unbind('keyup', plugin._keyup);
			$(document).unbind('mousedown', plugin._mousedown);
			$(this).removeData('data Zebra_DatePicker');
		}
	};
})(jQuery);
